/*
 * Document.java
 *
 * Created on 27 febbraio 2004, 18.59
 */
package net.ermannofranco.xml;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * Formalizza il documento nel suo insieme. Questa classe e' l'unica
 * istanziabile nel package. Da qui si parte per tutto l'albero xml.
 * 
 * @author ermanno (ermanno.franco@gmail.com)
 */
public class Doc extends Tag implements IDoc {

	private static final long serialVersionUID = -5086380812139731988L;
	public static final Namespace XSI_NS = new Namespace("xsi",
			"http://www.w3.org/2001/XMLSchema-instance");
	public static final Namespace XSD_NS = new Namespace("xsd",
			"http://www.w3.org/2001/XMLSchema");
	public static final Namespace XSL_NS = new Namespace("xsl",
			"http://www.w3.org/1999/XSL/Transform");

	/**
	 * Volendo, si puo' assegnare un nome di classe creatrice, per uso nel
	 * salvataggio di file con il metodo save(), a cui viene passato il nome del
	 * creatore+".xml"
	 */
	private static String DEFAULT_EXT = ".xml";
	private static final double DEFAULTVERSION = 1.0;
	/**
	 * DEFAULTENCODING e' LATIN-1
	 */
	private static final String DEFAULTENCODING = "iso-8859-1";
	protected Class<?> creator;// passa in Schema
	private Writer writer = new PrintWriter(System.out);
	private boolean writeStandaloneAttr = true;// scrive"standalone=..." nel
	// prologo
	private String completeDTDFileName;
	private String encoding;
	private double version = DEFAULTVERSION;
	// private ArrayList<String> namespaces;
	private Prop publicDoctype;
	private String systemDoctype;
	private String stylesheetPI;
	private String copyright;
	private boolean locatedSch;
	protected String extension = DEFAULT_EXT;
	private final DateFormat df = new SimpleDateFormat(
			"yyyy-MM-dd'T'HH:mm:ss.SSSZ");

	/**
	 * Creates a new instance of Document.
	 * 
	 * @param root
	 *            nome del tag radice
	 * @param encoding
	 *            stringa di encoding
	 * @param prefix
	 *            namespace globale
	 * @param xmlnsURI
	 *            indirizzo del namespace
	 * @param creator
	 *            classe creatrice
	 */
	// 1
	public Doc(String root, String encoding, Namespace ns, Class<?> creator) {
		super(root, 0, null);
		this.creator = creator;
		setEncoding(encoding);
		if (ns != null)
			setGlobalNS(ns);

	}

	// 2
	/**
	 * @param root
	 * @param prefix
	 * @param xmlnsURI
	 * @param creator
	 */
	public Doc(String root, Namespace ns, Class<?> creator) {
		this(root, DEFAULTENCODING, ns, creator);
	}

	// 3
	/**
	 * @param root
	 * @param creator
	 */
	public Doc(String root, Class<?> creator) {
		this(root, DEFAULTENCODING, null, creator);
		this.creator = creator;
	}

	// 4
	/**
	 * @param root
	 * @param prefix
	 * @param xmlnsURI
	 */
	public Doc(String root, Namespace ns) {
		this(root, DEFAULTENCODING, ns, null);
	}

	// 5
	/**
	 * @param root
	 */
	public Doc(String root) {
		this(root, DEFAULTENCODING, null, null);

	}

	/**
	 * restituisce ilnome del tag radice
	 * 
	 * @return nome del tag radice
	 */
	public String getRoot() {
		return getName();
	}

	/**
	 * stabilisce che non si vuole usare la dichiarazione 'standalone=....'; la
	 * scritta non compare
	 * 
	 * @return autoref
	 */
	public Doc omitStandaloneAttr() {
		writeStandaloneAttr = false;
		return this;
	}

	/**
	 * @param version
	 * @return xml version (currently 1.0)
	 */
	public Doc setXmlVersion(double version) {
		this.version = version;
		return this;
	}

	/**
	 * @return
	 * @param encoding
	 */
	public Doc setEncoding(String encoding) {
		this.encoding = encoding;
		return this;
	}

	/**
	 * @return
	 * @param copyright
	 */
	public Doc setCopyright(String copyright) {
		if (Static.isMinimalStr(copyright)) {
			throw new SyntaxException("copyright not valid, " + getName());
		}
		this.copyright = Static.toXmlString(copyright);
		return this;
	}

	/**
	 * @param location
	 * @return
	 */
	public Doc setNoNSSchemaLocation(String location) {
		if (Static.isMinimalStr(location)) {
			throw new SyntaxException("location non valida, " + getName());
		}
		addAttr("xsi:noNamespaceSchemaLocation", location);
		locatedSch = true;
		return this;
	}

	public Doc setSchemaLocation(String location) {
		if (Static.isMinimalStr(location)) {
			throw new SyntaxException("location non valida, " + getName());
		}
		addAttr("xsi:schemaLocation", location);
		locatedSch = true;
		return this;
	}

	// azzera l'altro
	/**
	 * @param name
	 * @param uri
	 * @return
	 */
	public Doc setPublicDoctype(String name, String uri) {
		publicDoctype = new Prop(name, uri);
		systemDoctype = null;
		return this;
	}

	// azzera l'altro
	/**
	 * @param uri
	 * @return
	 */
	public Doc setSystemDoctype(String uri) {
		systemDoctype = uri;
		publicDoctype = null;
		return this;
	}

	/**
	 * @param completeFileName
	 * @return
	 */
	public Doc loadDTD(String completeFileName) {
		completeDTDFileName = completeFileName;
		return this;
	}

	// metodo quasi callback, ha bisogno di sapere da chi � stato istanziato
	/**
	 * @return
	 */
	public final IDoc save(Indent indent) {
		File fi = extractFile();
		return save(fi, indent);
	}

	/**
	 * @return
	 */
	private File extractFile() {
		if (creator == null) {
			creator = this.getClass();
		}
		String fileN = creator.getSimpleName();
		File fi = new File(fileN);
		return fi;
	}

	/**
	 * @param fileName
	 * @return
	 */
	public final IDoc save(String fileName, Indent indent) {
		setSaveIndent(indent);
		save(fileName, this.toSave());
		return this;
	}

	/**
	 * @param file
	 * @return
	 */
	public final IDoc save(File file, Indent indent) {
		setSaveIndent(indent);
		save(file, extension, this.toSave());
		return this;
	}

	private void save(File file, String extension, String content) {
		String name = removeExtension(file.getAbsolutePath());
		setExtension(extension);
		save(name, content);
	}

	private String removeExtension(String name) {
		int lastIndOfBar = name.lastIndexOf(File.separatorChar);
		int lastIndOfDot = name.lastIndexOf('.');
		return lastIndOfDot > lastIndOfBar ? name.substring(0, lastIndOfDot)
				: name;
	}

	private void save(String fileNamewoutExt, String content) {
		try {
			String fileName = fileNamewoutExt + extension;
			FileWriter fw = new FileWriter(fileName);
			BufferedWriter bw = new BufferedWriter(fw);
			bw.write(content);
			bw.close();
			fw.close();
			log.info("File " + fileName + " salvato.");
			return;
		} catch (IOException ioe) {
			System.err.println("Impossibile salvare: " + ioe);
		}
	}

	public final String toSave() {
		StringBuilder sb = generateProlog();

		sb.append(super.toSave()).append(Static.CRLF);

		return sb.toString();
	}

	/**
	 * @return
	 */
	public final String toPrint() {

		StringBuilder sb = generateProlog();

		sb.append(super.toPrint()).append(Static.CRLF);

		return sb.toString();

	}

	/**
	 * @return
	 */
	private StringBuilder generateProlog() {
		Calendar cal = Calendar.getInstance();
		// prologo
		StringBuilder sb = new StringBuilder("<?xml version=\"" + version
				+ "\"");
		sb.append(" encoding=\"" + encoding).append(
				(writeStandaloneAttr ? "\" standalone=\""
						+ (isStandalone() ? "yes" : "no") : "")
						+ "\"?>");

		if (stylesheetPI != null) {
			sb.append("\r\n<?xml-stylesheet type=\"text/xsl\" href=\""
					+ stylesheetPI + "\"?>");
		}

		if (completeDTDFileName != null) {

			FileReader fr = null;
			StringBuilder sbfile = new StringBuilder();

			try {
				fr = new FileReader(completeDTDFileName);
			} catch (FileNotFoundException fnfe) {
				sbfile.append("\r\n<!-- file DTD \"" + completeDTDFileName
						+ "\" non trovato -->");
			}

			if (fr != null) {
				BufferedReader br = new BufferedReader(fr);
				String line = null;
				sbfile.append("\r\n<!DOCTYPE " + getRoot() + " [");

				try {
					while ((line = br.readLine()) != null) {
						sbfile.append("\r\n" + line);
					}
					br.close();
					fr.close();
				} catch (IOException ioe) {
					sbfile.append("\r\n<!-- attenzione: " + ioe + " -->");
				} finally {
					sbfile.append("\r\n]>");

				}
			}
			sb.append(sbfile.toString());
		}

		if (publicDoctype != null) {
			sb.append("\r\n<!DOCTYPE " + getRoot() + " PUBLIC \""
					+ publicDoctype.getName() + "\" \""
					+ publicDoctype.getValue() + "\" >");
		}
		if (systemDoctype != null) {
			sb.append("\r\n<!DOCTYPE " + getRoot() + " SYSTEM \""
					+ systemDoctype + "\">");
		}
		sb.append("\r\n<!-- " + df.format(cal.getTime()) + " -->");
		if (copyright != null) {
			sb.append("\r\n<!-- Copyright " + copyright + " "
					+ cal.get(Calendar.YEAR) + " -->\r\n");
		}
		return sb;
	}

	/**
	 * @param ps
	 * @return
	 */
	public final IDoc print(Indent indent) {
		setPrintIndent(indent);
		return printString();
	}

	public final IDoc logInfo() {
		log.info(this.toPrint());
		return this;
	}
	public final IDoc logInfo(Indent indent) {
		setPrintIndent(indent);
		log.info(this.toPrint());
		return this;
	}	
	/**
	 * @return
	 */
	private IDoc printString() {
		try {
			writer.write(this.toPrint());
			writer.flush();
		} catch (IOException e) {
			throw new RuntimeException(
					"Impossibile scrivere nel Writer specificato", e);
		}
		return this;
	}

	public IDoc print(Writer out, Indent indent) {
		setWriter(out);
		return print(indent);
	}

	/**
	 * Lo stesso che addNS, ma restituisce Doc, non AbstractTag (qui non ci sono
	 * generics...)
	 * 
	 * @param prefix
	 * @param uri
	 * @return
	 */
	public Doc addNamespace(Namespace ns) {
		addNS(ns);
		return this;
	}

	/**
	 * @param ns
	 * @param uri
	 * @return
	 */
	public Doc setGlobalNS(Namespace ns) {
		setLocalNS(ns);
		return this;
	}

	/**
	 * @return
	 */
	public Doc addCanonicNS() {
		addNS(XSD_NS).addNS(XSI_NS).addNS(XSL_NS);
		return this;
	}

	/**
	 * @param uri
	 * @return autoref
	 */
	public Doc setStylesheetRef(String uri) {
		if (uri != null && uri.length() > 0) {
			stylesheetPI = uri;
		}
		return this;
	}

	/**
	 * @param writer
	 *            oggetto che stampa, di default e' System.out
	 * @return autoref
	 */
	private IDoc setWriter(Writer writr) {
		if (writr != null) {
			this.writer = writr;
		}
		return this;
	}

	/**
	 * @return se e' standalone
	 */
	public boolean isStandalone() {
		return publicDoctype == null && systemDoctype == null && !locatedSch;
	}

	/*
	 * private String checkFile(String fileName, String extension) { boolean
	 * withExtension=false; int lastIndOfDot=fileName.lastIndexOf('.'); int
	 * lastIndOfSlsh=fileName.lastIndexOf(java.io.File.separatorChar); if
	 * (lastIndOfDot>lastIndOfSlsh) withExtension=true;
	 * 
	 * if (!withExtension) fileName=fileName.replace('.', '_'); if
	 * (!withExtension) fileName=fileName + "." + extension;
	 * 
	 * return fileName; }
	 */
	/**
	 * @return .xml
	 */
	// public String getDefaultExtension() {
	// return "." + DEFAULTID;
	// }

	public Doc setExtension(String extension) {
		if (extension.indexOf(".") != 0)
			extension = "." + extension;
		this.extension = extension;
		return this;
	}

	/**
	 * Lascia le impostazioni di indent per ogni tag, senza sovrascriverle.
	 */
	public IDoc print() {
		return printString();
	}
	public IDoc print(BlankLine blankLine) {
		setBlankLine(blankLine);
		return printString();
	}	

	/**
	 * Lascia le impostazioni di indent per ogni tag, senza sovrascriverle.
	 */
	public IDoc print(Writer writer) {
		setWriter(writer);
		return printString();
	}

	/**
	 * Lascia le impostazioni di indent per ogni tag, senza sovrascriverle.
	 */
	public final IDoc save(File file) {
		save(file, extension, this.toSave());
		return this;
	}

	/**
	 * Lascia le impostazioni di indent per ogni tag, senza sovrascriverle.
	 */
	public final IDoc save(String fileName) {
		save(fileName, this.toSave());
		return this;
	}

	/**
	 * Lascia le impostazioni di indent per ogni tag, senza sovrascriverle.
	 */
	public final IDoc save() {
		File fi = extractFile();
		return save(fi);
	}

	@Override
	public final String toString() {
		return toPrint();
	}

	/**
	 * Add a generic comment in the middle of document, not bound to any tag.
	 */
	public IDoc addCommentedText(String comment) {
		Node n = new CommentNode(comment, getLevel() + 1);
		addNode(n);
		return this;
	}

}
